/**
 * 
 * The MIT License
 * 
 * Copyright (c) 2008 the original author or authors.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.googlecode.gchartjava;

import static com.googlecode.gchartjava.ArraysUtil.asUnmodifiableList;
import static java.util.Collections.unmodifiableList;

import java.util.LinkedList;
import java.util.List;

import com.googlecode.gchartjava.BarChartDataSeries.DataLine;

/**
 * 
 * Bar chart class that supports vertical and horizontal bar charts. Bars from
 * different data series can either be stacked or side-by-side.
 * 
 * @see BarChartDataSeries
 * @author Julien Chastang (julien.c.chastang at gmail dot com)
 */
public class BarChart extends AbstractAxisChart {
	private static final String BAR_CHART_URL    = GCHART_URL + "?";
	private static final String CHART_TYPE_PARAM = "cht=b";
	private static final String BAR_CONFIG       = "chbh=";
	private static final String MARKER_PARAM     = "chm=";


	private boolean dataStacked          = false;
	private boolean horizontal           = false;
	private int barWidth                 = 23; // This is the default, as far as I can tell.
	private int spaceBetweenGroupsOfBars = 8;  // This is the default, as far as I can tell.
	private int spaceWithinGroupsOfBars  = 4; // This is the default, as far as I can tell.

	private final List<BarChartDataSeries> barSeriesList;

	/**
	 * Create a bar chart.
	 * 
	 * @param barChartData
	 *            A bar chart data series.
	 * @see BarChartDataSeries
	 */
	public BarChart(final BarChartDataSeries... barChartData) {
		super();
		this.barSeriesList = asUnmodifiableList(barChartData);
	}

	/**
	 * Create a bar chart.
	 * 
	 * @param barChartData
	 *            A bar chart data series.
	 * @see BarChartDataSeries
	 */
	public BarChart(final List<? extends BarChartDataSeries> barChartData) {
		this.barSeriesList = unmodifiableList(barChartData);
	}

	/**
	 * Are the bar charts stacked or side-by-side.
	 * 
	 * @param dataStacked
	 *            If true, data from different bar chart series will be stacked
	 *            instead of side-by-side.
	 */
	public void setDataStacked(final boolean dataStacked) {
		this.dataStacked = dataStacked;
	}

	/**
	 * Is the bar chart bars horizontal or vertical.
	 * 
	 * @param horizontal
	 *            If true, the bar chart will be horizontal instead of vertical.
	 */
	public void setHorizontal(final boolean horizontal) {
		this.horizontal = horizontal;
	}

	/**
	 * 
	 */
	@Override
	protected void prepareData() {
		final List<Data> datas        = new LinkedList<Data>();
		final List<String> legends    = new LinkedList<String>();
		final List<Color> colors      = new LinkedList<Color>();
		final List<String> markerList = new LinkedList<String>();
		
		boolean hasLegend = false;
		
		for (BarChartDataSeries series : barSeriesList) {
			hasLegend |= (series.getLegend() != null);
		}
		
		int index = 0;
		for (BarChartDataSeries series : barSeriesList) {
			datas.add(series.getData());
			if (hasLegend) {
				legends.add((series.getLegend() != null) ? series.getLegend() : "+");
			}
			colors.add(series.getColor());
			if (series.getDataLine() != null)
				markerList.add(stringifyLine(series.getDataLine(), index));
			if (series.getFillAreaColor() != null)
				markerList.add(stringifyFillAreaColor(series.getFillAreaColor(), index));
			index++;
		}
		
		final String markers = stringifyList(markerList);

		setData(unmodifiableList(datas));
		setLegends(unmodifiableList(legends));
		setColors(unmodifiableList(colors));
		addParameter(BAR_CONFIG, barWidth + "," + spaceWithinGroupsOfBars  +  "," + spaceBetweenGroupsOfBars);
		if (markers.length() > 0)
			addParameter(MARKER_PARAM, markers);
		setAxisLabels();
	}

	/**
	 * Serialize to string the data line and fill area list.
	 */
	private String stringifyList(final List<String> strings) {
		String str= "";
		int cnt = 0;
		for (String s : strings) {
			str += cnt++ > 0 ? "|" + s : s;
		}
		return str;
	}

	/**
	 * Serialize to string the data line.
	 */
	private String stringifyLine(final DataLine dataLine, final int index) {
		if (dataLine == null)
			return "";
		else
			return "D," + dataLine.getColor() + "," + index + ",0," + dataLine.getDataLineSize() + "," + dataLine.getPriority();
	}

	/**
	 * Serialize to string the fill area color. 
	 */
	private String stringifyFillAreaColor(final Color fillAreaColor, final int index) {
		if (fillAreaColor == null)
			return "";
		else
			return "B," + fillAreaColor + "," + index + ",0,0"; 
	}

	/**
	 *		
	 */
	@Override
	protected String getBaseURLString() {
		return BAR_CHART_URL + CHART_TYPE_PARAM + ((horizontal) ? "h" : "v") + ((dataStacked) ? "s" : "g");
	}

	/**
	 * Set the bar chart width.
	 * 
	 * @param barWidth
	 *            Bar width in pixels. If not set defaults to 23.
	 */
	public void setBarWidth(final int barWidth) {
		this.barWidth = barWidth;
	}

	/**
     * Set the space between groups of bars.
	 * 
	 * @param spaceBetweenGroupsOfBars
	 *            The space between groups of bars. Bars within a group have
	 *            half the specified spacing. If not set default to 8.
	 */
	public void setSpaceBetweenGroupsOfBars(final int spaceBetweenGroupsOfBars) {
		this.spaceBetweenGroupsOfBars = spaceBetweenGroupsOfBars;
	}
	
	/**
     * Set the space within groups of bars.
	 * 
	 * @param spaceWithinGroupsOfBars
	 *            The space within groups of bars. Bars within a group have
	 *            half the specified spacing. If not set default to 4.
	 */
	public void setSpaceWithinGroupsOfBars(final int spaceWithinGroupsOfBars) {
		this.spaceWithinGroupsOfBars = spaceWithinGroupsOfBars;
	}

}
